﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;


namespace Engine.Dtd.Schema {
    /// <summary>
    /// Represents the collection of elements combined by group connectors and may also be modified by occurrence indicators.
    /// </summary>
    [DebuggerDisplay("{ToString()}")]
    [DebuggerNonUserCode]
    public sealed partial class DModelGroups : IModelGroupsElement, IModelGroupsNode, IEnumerable<DElement> {
        [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
        private List<IModelGroupsNode> elements;

        /// <summary>
        /// Represents the empty DTD model groups.
        /// </summary>
        /// <remarks>
        /// This field is read-only.
        /// </remarks>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        internal static readonly DModelGroups Empty = new DModelGroups();

        internal DModelGroups() {
            this.elements = new List<IModelGroupsNode>();
            }

        /// <summary>
        /// Initialize a new instance of the DModelGroups object with specified order and
        /// occurrence type.
        /// </summary>
        /// <param name="order">An order type of the model groups, defining the order in which child elements can occur.</param>
        /// <param name="occurrence">An occurrence of the model groups.</param>
        public DModelGroups(DGroupOrderType order, DElementOccurrence occurrence)
            : this() {
            this.OrderType = order;
            this.Occurrence = occurrence;
            }

        /// <summary>
        /// Adds a DModelGroups object to the end of this model groups.
        /// </summary>
        /// <param name="group">A DModelGroups object to be added.</param>
        /// <exception cref="System.ArgumentNullException">If a group is null.</exception>
        /// <exception cref="System.ArgumentException">If the model groups does not contain any elements.</exception>
        public void Add(DModelGroups group) {
            Insert(group, Count);
            }

        /// <summary>
        /// Inserts a model groups into this model groups at the specified index.
        /// </summary>
        /// <param name="group">A DModelGroups object to insert.</param>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        public void Insert(DModelGroups group, int index) {
            Guard.ArgumentNotNull(group, "group");
            if (group.Count == 0)
                throw new ArgumentException("The model groups does not contain any elements.");
            if (index < 0 || index > Count)
                throw new ArgumentOutOfRangeException("index", "The value must be more than or equal zero and less than count of elements.");
            // Remove model groups if it exist in another location
            if (group.parent != null)
                group.parent.Remove(group);
            group.orderIndex = elements.Count;
            group.parent = this;
            elements.Insert(index, group);
            }

        /// <summary>
        /// Adds a DElement object to the and of this model groups.
        /// </summary>
        /// <param name="element">A DElement object to be added.</param>
        /// <exception cref="System.ArgumentNullException">If an element is null.</exception>
        public void Add(DElement element) {
            Insert(element, Count);
            }

        /// <summary>
        /// Inserts an element into this model groups at the specified index.
        /// </summary>
        /// <param name="element">A DElement object to insert.</param>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        public void Insert(DElement element, int index) {
            Guard.ArgumentNotNull(element, "element");
            if (index < 0 || index > Count)
                throw new ArgumentOutOfRangeException("index", "The value must be more than or equal zero and less than count of elements.");
            // Remove element if it exist in another location.
            element.Remove();
            element.SetOrderIndex(elements.Count);
            element.SetParent(this);
            elements.Insert(index, element);
            }

        /// <summary>
        /// Determines whether an item is in this model gorups.
        /// </summary>
        /// <param name="item">An element to locate in this model groups.</param>
        /// <returns>true if item is found in the collection; otherwise, false.</returns>
        public bool Contains(IModelGroupsElement item) {
            for (int i = 0; i < elements.Count; i++) {
                if (object.ReferenceEquals(elements[i], item))
                    return true;
                }
            return false;
            }

        /// <summary>
        /// Removes the first occurrence of a specific item from this model groups.
        /// </summary>
        /// <param name="item">An element to remove from the model groups.</param>
        /// <returns>true if item is successfully removed; otherwise, false.</returns>
        public bool Remove(IModelGroupsElement item) {
            for (int i = 0; i < Count; i++) {
                if (object.ReferenceEquals(elements[i], item)) {
                    elements.RemoveAt(i);
                    return true;
                    }
                }
            return false;
            }

        internal bool Contains(DName name) {
            if (Count == 0)
                return false;
            if (parent == null) {
                IModelGroupsNode next = this.NextGroupItem();
                while (next != null) {
                    if (next.Name == name)
                        return true;
                    next = next.NextGroupItem();
                    }
                return false;
                }
            else {
                foreach (IModelGroupsNode item in elements) {
                    if (item.IsModelGroups()) {
                        if (item.FirstItem.Parent.Contains(name))
                            return true;
                        }
                    else if (item.Name == name)
                        return true;
                    }
                return false;
                }
            }

        /// <summary>
        /// Gets the number of components actually contained in this model.
        /// </summary>
        public int Count {
            get { return elements.Count; }
            }

        /// <summary>
        /// Gets the specified component at the specified index.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        internal IModelGroupsNode this[int index] {
            get { return elements[index]; }
            }

        internal IModelGroupsNode this[DName name] {
            get {
                if (name == null)
                    return null;
                for (int i = 0; i < elements.Count; i++)
                    if (elements[i].Name == name)
                        return elements[i];
                return null;
                }
            }

        /// <summary>
        /// Returns an enumerator that iterates through the components of this model and its descendants.
        /// </summary>
        /// <returns>An IEnumerator&lt;IModelGroupsElement&gt; that contains the collection's members.</returns>
        public IEnumerator<DElement> GetEnumerator() {
            return new Iterator(this);
            }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
            }

        /// <summary>
        /// Gets the occurrence indicator for this model.
        /// </summary>
        public DElementOccurrence Occurrence { get; internal set; }

        /// <summary>
        /// Gets the order in which the components of this model may appear.
        /// </summary>
        public DGroupOrderType OrderType { get; internal set; }


        DName IModelGroupsElement.Name {
            get { return null; }
            }

        DElementOccurrence IModelGroupsElement.Occurrence {
            get { return this.Occurrence; }
            }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private DModelGroups parent;
        DModelGroups IModelGroupsElement.Parent {
            get { return parent; }
            }

        DElementDeclaration IModelGroupsNode.Declaration {
            get { return null; }
            }

        IModelGroupsNode IModelGroupsNode.FirstItem {
            get {
                if (Count > 0)
                    return elements[0];
                else
                    return null;
                }
            }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private int orderIndex;
        int IModelGroupsNode.OrderIndex {
            get { return orderIndex; }
            }

        /// <summary>
        /// Gets the first component of this model.
        /// </summary>
        internal IModelGroupsNode FirstItem {
            get {
                if (Count > 0)
                    return elements[0];
                else
                    return null;
                }
            }

        /// <summary>
        /// Returns a string that represents the current model groups.
        /// </summary>
        /// <returns>A string in the DTD language schema format.</returns>
        public override string ToString() {
            if (elements.Count == 0)
                return string.Empty;
            char orderChar = char.MinValue;
            switch (OrderType) {
                case DGroupOrderType.OnlyOne:
                    orderChar = Characters.VerticalBar;
                    break;
                case DGroupOrderType.Sequence:
                    orderChar = Characters.Comma;
                    break;
                case DGroupOrderType.AnyOrder:
                    orderChar = Characters.Ampersand;
                    break;
                default:
                    Debug.Fail("Unknown the OrderType of this DModelGroups object.");
                    orderChar = Characters.VerticalBar;
                    break;
                }
            StringBuilder builder = new StringBuilder();
            builder.Append(Characters.LeftParenthesis);
            for (int i = 0; i < elements.Count - 1; i++) {
                builder.Append(elements[i].ToString());
                builder.Append(Characters.Whitespace);
                builder.Append(orderChar);
                builder.Append(Characters.Whitespace);
                }
            Debug.Assert(elements.Count > 0, "The model groups contains no elements.");
            builder.Append(elements.Last().ToString());
            builder.Append(Characters.RightParenthesis);
            switch (Occurrence) {
                case DElementOccurrence.OnlyOnce:
                    break;
                case DElementOccurrence.OneOrMore:
                    builder.Append(Characters.Plus);
                    break;
                case DElementOccurrence.ZeroOrMore:
                    builder.Append(Characters.Asterix);
                    break;
                case DElementOccurrence.ZeroOrOne:
                    builder.Append(Characters.QueryMark);
                    break;
                default:
                    Debug.Fail("Unknown the Occurrence of the current DModelGroups object.");
                    break;
                }
            return builder.ToString();
            }

        /// <summary>
        /// Parse the group connector from specified char.
        /// </summary>
        /// <param name="order">A char that contains the connector value.</param>
        /// <exception cref="DtdException">DtdException</exception>
        /// <returns>A DGroupOrderType instance.</returns>
        public static DGroupOrderType ParseGroupOrderType(char order) {
            switch (order) {
                case Characters.Comma:
                    return DGroupOrderType.Sequence;
                case Characters.VerticalBar:
                    return DGroupOrderType.OnlyOne;
                case Characters.Ampersand:
                    return DGroupOrderType.AnyOrder;
                default:
                    throw new DtdException(string.Format("Can not recognize specified elements order type '{0}'.", order));
                }
            }
        }
    }
